package org.homonoia.controller.queue;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;
import org.homonoia.model.core.Status;
import org.homonoia.model.query.Query;
import org.homonoia.model.query.QueryID;
import org.homonoia.model.query.QueryResults;
import org.homonoia.model.query.QueryStatus;
import org.homonoia.model.task.Task;
import org.homonoia.model.task.TaskResults;

public class QueuedJob {
    private static final Logger log = Logger.getLogger(QueuedJob.class);

    private QueryID id = null;
    private ArrayList<String> searchTerms = null;
    private String table = null;
    private String column = null;

    private Map<Task, Status> taskList;
    private Map<String, String> taskNode;
    private ArrayList<TaskResults> taskResults = null;

    volatile private QueryResults jobResults = null;

    volatile private Date startTime = null;
    volatile private Date endTime = null;

    public QueuedJob(Query job) {
	this.id = new QueryID();
	this.searchTerms = job.getSearchTerms();
	this.table = job.getTable();
	this.column = job.getColumn();

	this.taskList = new ConcurrentHashMap<Task, Status>();
	this.taskNode = new ConcurrentHashMap<String, String>();
	this.taskResults = new ArrayList<TaskResults>();
    }

    public final QueryID getJobID() {
	return id;
    }

    public final QueryStatus getJobStatus() {
	Status status = null;

	synchronized (taskList) {
	    if (taskList.containsValue(Status.RUNNING)) {
		status = Status.RUNNING;
	    } else if (taskList.containsValue(Status.WAITING)
		    || (taskList.isEmpty() && jobResults == null)) {
		status = Status.WAITING;
	    } else if (taskList.containsValue(Status.UNKNOWN)) {
		status = Status.UNKNOWN;
	    } else {
		status = Status.COMPLETED;
	    }
	}

	return new QueryStatus(status, startTime, endTime);
    }

    public final QueryResults getJobResults() {
	return jobResults;
    }

    public final Date getStartTime() {
	return startTime;
    }

    public final Date getEndTime() {
	return endTime;
    }

    public void generateTasks(Properties configProps) {
	int rowCount, nodesPerJob, rowsPerTask;
	startTime = new Date();

	Connection conn = null;
	Properties connectionProps = new Properties();
	connectionProps.put("user", configProps.getProperty("DBUser"));
	connectionProps.put("password", configProps.getProperty("DBPass"));

	try {
	    conn = DriverManager.getConnection("jdbc:" + configProps.getProperty("DBUrl"),
		    connectionProps);

	    Statement stmt = conn.createStatement();
	    String sql = "SELECT Count(*) AS NumRows FROM " + table;
	    ResultSet rs = stmt.executeQuery(sql);

	    if (rs.first()) {
		rowCount = rs.getInt("NumRows");
		nodesPerJob = Integer.parseInt(configProps.getProperty("NodesPerJob"));
		rowsPerTask = rowCount / nodesPerJob;
	    } else {
		throw new SQLException("No rows in result set.");
	    }

	    stmt.close();

	    for (int i = 0; i < nodesPerJob; ++i) {
		Task task = new Task(id.toString() + "_" + i, (i * rowsPerTask) + 1,
			((i + 1) * rowsPerTask), searchTerms, table, column);

		synchronized (taskList) {
		    taskList.put(task, Status.WAITING);
		}

		synchronized (taskNode) {
		    taskNode.put(task.getID(), "");
		}
	    }
	} catch (SQLException e) {
	    log.error(e.getMessage(), e);
	} finally {
	    try {
		if (conn != null) {
		    conn.close();
		}
	    } catch (SQLException e) {
		log.error(e.getMessage(), e);
	    }
	}
    }

    public void addTaskResults(final TaskResults results) {
	log.info("Task: " + results.getID() + " complete.");

	synchronized (taskList) {
	    for (Map.Entry<Task, Status> entry : taskList.entrySet()) {
		if (entry.getKey().getID().equals(results.getID())) {
		    taskList.remove(entry.getKey());
		}
	    }
	}

	synchronized (taskNode) {
	    for (Map.Entry<String, String> entry : taskNode.entrySet()) {
		if (entry.getKey().equals(results.getID())) {
		    taskNode.remove(entry.getKey());
		}
	    }
	}

	synchronized (taskResults) {
	    taskResults.add(results);
	}
    }

    public void setJobResults(QueryResults results) {
	endTime = new Date();

	synchronized (taskResults) {
	    taskResults.clear();
	}

	jobResults = results;
    }

    public boolean hasUnassignedTasks() {
	synchronized (taskList) {
	    if (taskList.containsValue(Status.WAITING)) {
		return true;
	    }
	}
	return false;
    }

    public final Task getNextUnassignedTask() {
	synchronized (taskList) {
	    for (Map.Entry<Task, Status> entry : taskList.entrySet()) {
		if (entry.getValue() == Status.WAITING) {
		    return entry.getKey();
		}
	    }
	}
	return null;
    }

    public void setTaskRunning(final String taskID, final String node) {
	synchronized (taskList) {
	    for (Map.Entry<Task, Status> entry : taskList.entrySet()) {
		if (entry.getKey().getID() == taskID) {
		    entry.setValue(Status.RUNNING);
		}
	    }
	}

	synchronized (taskNode) {
	    for (Map.Entry<String, String> entry : taskNode.entrySet()) {
		if (entry.getKey() == taskID) {
		    entry.setValue(node);
		}
	    }
	}
    }

    public boolean allSearchTasksComplete() {
	boolean isComplete = false;

	synchronized (taskList) {
	    isComplete = taskList.isEmpty();
	}

	synchronized (taskNode) {
	    isComplete = taskNode.isEmpty();
	}

	return isComplete;
    }

    public Task generateJoinTask() {
	return new Task(id.toString() + "_join", taskResults);
    }

    public void checkAndRescheduleTask(String node) {
	synchronized (taskNode) {
	    for (Map.Entry<String, String> entry : taskNode.entrySet()) {
		if (entry.getValue().equalsIgnoreCase(node)) {
		    entry.setValue("");
		    synchronized (taskList) {
			for (Map.Entry<Task, Status> ent : taskList.entrySet()) {
			    if (ent.getKey().getID() == entry.getKey()
				    && ent.getValue() != Status.COMPLETED) {
				ent.setValue(Status.WAITING);
			    }
			}
		    }
		}
	    }
	}
    }

}
